/*!***************************************************
 * @file: core.c
 * @brief: 本程序的总调度层
 * @author: ITHAO 
 * @date: 2018.11.19
 * @note: 
 ****************************************************/
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/ipc.h>
#include <setjmp.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/select.h>
#include <pthread.h>

#include "debug.h"
#include "comm_protocol_core.h"
#include "app.h"
#include "login_app.h"
#include "func_app.h"
#include "interface.h"

extern jmp_buf ctrl_c_jump;

static void ctrl_c_handle(int sig);
static void *client_recv_thread(void *p_app);

/**
 * @brief	程序初始化部分     
 * @param	p_app--总调度结构体指针
 * @return	void
 * @note:
 */
int init(struct app *p_app)
{
    int ret = 0;


    /*段错误处理初始化*/
#ifdef _CORE_DUMP_
    core_dump_init();
#endif

    if (access("./log", F_OK) != 0) 
        mkdir("./log", 0777);
    login_info_init(&p_app->user_login_info);
    friend_list_head_init(&p_app->friend_list_head);
    pthread_mutex_init(&p_app->mutexfd.mutex, NULL);

    /*创建接收线程*/
    ret = pthread_create(&p_app->recv_thread, NULL, 
                        client_recv_thread, p_app);
    if (ret != 0) {
        DEBUG("接收线程创建失败\r\n");
        return (-APP_CREATE_PTHREAD_ERR);
    }
    
    /*设置ctrl+c信号处理函数*/
    signal(SIGINT, ctrl_c_handle);

    return TRUE;
}


/**
 * @brief	程序运行部分     
 * @param	p_app--程序调度层结构体
 * @return	-app_PARAM_NULL        入口参数为空
 * @note:
 */
int run(struct app *p_app)
{
    struct comm_core comm;
    struct interface interface; 

    if (!p_app) {
        DEBUG("PARAM ERROR!\n");
        return (-APP_PARAM_NULL);
    }

    /*通信功能调度结构体初始化*/
    comm_core_init(&comm);
    comm.dev.cmd = COMM_CLIENT_INIT;
    p_app->mutexfd.clientfd = comm.ioctl(&comm.dev, 
                              comm.dev.cmd, comm.dev.arg);
  
    /*执行界面*/
    interface.p_app = p_app;
    p_app->p_interface = &interface;
    view_run(&interface);

    comm_core_destory(&comm);
    return TRUE;
}


/**
 * @brief	程序结束部分，负责程序退出前的收尾工作     
 * @param	p_app--程序调度层结构体
 * @return	-APP_PARAM_NULL        入口参数为空
 * @note:   按下CTRL+C后会跳到这里来执行完后，程序才结束
 */
int destroy(struct app *p_app)
{
    if (!p_app) {
        DEBUG("PARAM ERROR!\n");
        return (-APP_PARAM_NULL);
    }

    pthread_cancel(p_app->recv_thread);
    destroy_friend_list(&p_app->friend_list_head);
    pthread_mutex_destroy(&p_app->mutexfd.mutex);

    free(p_app);

    return TRUE;
}

/**
 * @brief	接收线程
 *          一直接受从服务器发过来的消息并进行处理    
 * @param	p_app--程序调度层结构体
 * @return
 * @note:   因为通信做了互斥所以这里不会发生冲突
 */
static void *client_recv_thread(void *p_app)
{
    int ret = 0;
    fd_set setfd;
    struct comm_core comm;
    struct timeval timeout;
    int ack;
    int cmd;
    struct chat_packet chat_packet;
    /*一下变量都要用指针操作，因为换用户可能就不一样了，
        而且也不能保证当前线程和主线程谁先执行*/
    struct list_head *p_friend_list_head = NULL;
    char *account;
    int *clientfd = NULL;
    pthread_mutex_t *mutex = NULL;

    if (!p_app) {
        DEBUG("PARAM ERROR!\n");
        pthread_exit((void *)(-APP_PARAM_NULL));
    }

    p_friend_list_head = &((struct app *)p_app)->friend_list_head;
    account = ((struct app *)p_app)->user_login_info.account;
    clientfd = &((struct app *)p_app)->mutexfd.clientfd;
    mutex = &((struct app *)p_app)->mutexfd.mutex;

    comm_core_init(&comm);
    comm.dev.cmd = COMM_RECV;

    FD_ZERO(&setfd);
    timeout.tv_sec = 0;
    timeout.tv_usec = 100;

    while (1) {
        comm.data.recvfd = *clientfd;
        FD_SET(*clientfd, &setfd);
        /*做互斥*/
        pthread_mutex_lock(mutex);
        ret = select(*clientfd + 1, &setfd, NULL, NULL, &timeout);
        if (ret < 0) {
            DEBUG("SELECT FAIL!\r\n");
            pthread_mutex_unlock(mutex);
            continue;
        } 
        /*select超时*/
        if (ret == 0) 
            /*这里和上面的情况别忘了解互斥*/
            pthread_mutex_unlock(mutex);
        /*检测到有信息可读*/
        if (ret > 0  && FD_ISSET(*clientfd, &setfd)) {
           /*接受信息*/
            comm.ioctl(&comm.dev, comm.dev.cmd, comm.dev.arg);
            /*接收完解互斥*/
            pthread_mutex_unlock(mutex);
            ack = comm.data.recv_data_packet.ack;
            cmd = comm.data.recv_data_packet.cmd;

            /*这里根据收到的信息进行相应的处理*/
            switch(comm.data.recv_data_packet.cmd_type) {
                /*收到有新好友或者是少了好友就重新加载好友*/
                case FRIEND_CMD_TYPE:
                    /*有好友上线*/
                    if (cmd == FRIEND_ONLINE_CMD) {
                        change_friend_online_flag((char *)comm.data.recv_data_packet.data[0],
                                                  1,
                                                  p_friend_list_head);
                    /*有好友下线*/
                    } else if (cmd == FRIEND_OFFLINE_CMD) {
                        change_friend_online_flag((char *)comm.data.recv_data_packet.data[0],
                                                  0,
                                                  p_friend_list_head);
                    /* 有新好友或者失去好友 */
                    } else {
                        if (ack == ADD_GET_NEW_FRIEND_ACK ||
                            ack == REMOVE_LOSE_FRIEND_ACK) {
                            destroy_friend_list(p_friend_list_head);
                            ret = load_friend(&((struct app *)p_app)->mutexfd,
                                            account,
                                            p_friend_list_head);
                            updata_friend_store(((struct app *)p_app)->p_interface); 
                        }
                    }
                    break;

                /*有聊天信息*/
                case CHAT_CMD_TYPE:
                    chat_packet.send_name = (char *)comm.data.recv_data_packet.data[0];
                    chat_packet.send_account = (char *)comm.data.recv_data_packet.data[1];
                    chat_packet.recv_account = account;
                    chat_packet.send_time = (char *)comm.data.recv_data_packet.data[2];
                    chat_packet.msg = (char *)comm.data.recv_data_packet.data[3];
                    save_chat_history(chat_packet, 1);

                     if (((struct app *)p_app)->p_interface->view.next_view == CHAT_V &&
                        !strcmp(((struct app *)p_app)->p_interface->chating_friend_account,
                        chat_packet.send_account)) {
                         gtk_text_buffer_insert(((struct app *)p_app)->p_interface->view.chat_view_data.buffer_history,
								   &((struct app *)p_app)->p_interface->view.chat_view_data.history_end,
                                    chat_packet.send_name, -1);
			             gtk_text_buffer_insert(((struct app *)p_app)->p_interface->view.chat_view_data.buffer_history, 
						   		   &((struct app *)p_app)->p_interface->view.chat_view_data.history_end,
                                    "\t", -1);
			            gtk_text_buffer_insert(((struct app *)p_app)->p_interface->view.chat_view_data.buffer_history, 
								   &((struct app *)p_app)->p_interface->view.chat_view_data.history_end,
                                    chat_packet.send_time, -1);
			            gtk_text_buffer_insert(((struct app *)p_app)->p_interface->view.chat_view_data.buffer_history, 
								   &((struct app *)p_app)->p_interface->view.chat_view_data.history_end,
                                    "\n\t", -1);
			            gtk_text_buffer_insert(((struct app *)p_app)->p_interface->view.chat_view_data.buffer_history, 
								   &((struct app *)p_app)->p_interface->view.chat_view_data.history_end,
                                    chat_packet.msg, -1);
			            gtk_text_buffer_insert(((struct app *)p_app)->p_interface->view.chat_view_data.buffer_history, 
								   &((struct app *)p_app)->p_interface->view.chat_view_data.history_end,
                                    "\n", -1); 
                    } 
                    
                    break;

                default:
                    break;
            }
        }
        FD_ZERO(&setfd);
        timeout.tv_sec = 0;
        timeout.tv_usec = 100;
        /*这里时间有点长，看到时候要不要改短一点*/
        sleep(1);
    }
}


/*Ctrl+C触发信号处理函数*/
static void ctrl_c_handle(int sig)
{
	printf("\n\tpress Ctrl+C will exit\n");
    
	longjmp(ctrl_c_jump, 1);
}